using System;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;


namespace NeuralNetwork
{
	public class Network
	{
		public List<Layer> perceptron;
        public List<double> data; 
		
		public void fillInputsWithdata (List<String> data)
		{
			Random random = new Random();
			
			foreach (Neuron neuron in perceptron[0])
                {
                    foreach (String dataString in data)
                    {
                        Input input = new Input(neuron, random.NextDouble(), Double.Parse(dataString));
                        neuron.addInputs(input);
                    }
                }
				
			
		}

        public void newData(List<String> data)
        {
            foreach (Neuron neuron in perceptron[0])
            {
                for (int i = 0; i < data.Count; i++)
			    {
			        neuron.inputs[i].data = Double.Parse(data[i]);
			    }
	        }              
        } 
            
        

        private void activateNeuron (Neuron perceptron, Layer layer, List<double> outputs){
            //perceptron.getInput ();
            perceptron.computeOutput ();
            if (layer.name.Equals("Output Layer"))
            {
                //lock ouputs so there is no dispute when the outputs.Add is called
                 lock (outputs)
                 { 
                     outputs.Add(perceptron.output);
                 }
            }
                    
        }

        public List<double> pushUpAnswer()
        {
            List<double> outputs = new List<double>();

            foreach (Layer layer in perceptron)
            {
               Parallel.ForEach(layer, neuron => activateNeuron(neuron, layer, outputs));
            }
            return outputs;
        }
		
		public Network (int numberOfInputs, int numberOfOutputs, int numberOfHiddenLayers)
		{
			perceptron = new List<NeuralNetwork.Layer> ();

			//add the hidden layers
			for (int i = 0; i < numberOfHiddenLayers; i++) {
				perceptron.Add (new Layer ("Hidden Layer #" + i.ToString()));
			}
			//add the output layer
			perceptron.Add (new Layer ("Output Layer"));
			
			int mean = (numberOfInputs + numberOfOutputs) / 2;
            //must have at least 2 perceptrons in the hidden layer
            if (mean == 1)
                mean = 2;
			
			int hiddenLayerCounter = 0;
			
			foreach (Layer layer in perceptron) {
				if (layer.name.Contains ("Hidden Layer")) {
					for (int i = 0; i < mean; i++) {
						layer.Add (new Neuron (layer, i));
					}
					hiddenLayerCounter++;
				}
				else {
					for (int i = 0; i < numberOfOutputs; i++) {
						layer.Add (new Neuron (layer, i));
					}
				}
			}
			
			Random random = new Random ();
			
			foreach (Layer layer in perceptron) {
				if (layer.Equals (perceptron[0])) {
				}
				else {
					int index = perceptron.IndexOf (layer);
					foreach (Neuron p in layer)
                    {
						foreach (Neuron q in perceptron[index - 1])
						{
                           double number;
							if (random.NextDouble() > .5)
                                number = random.NextDouble();
                            else
                                number = - random.NextDouble();
                                 
							Input input = new Input (q, p, number, Double.NaN);
                            
							p.addInputs (input);
							q.addOutputs (input);
						}
					}
				}		
			}
		}			
	}
}